package com.media.selector.media.model.loader

import android.content.Context
import android.database.Cursor
import android.database.MatrixCursor
import android.database.MergeCursor
import android.net.Uri
import android.provider.MediaStore
import androidx.annotation.NonNull
import androidx.annotation.Nullable
import com.media.selector.media.model.SelectionSpec
import com.media.selector.media.model.entity.AlbumEntity

/**
 * date        ：2020/5/11
 * author      ：蒙景博
 * description ：
 */
class AlbumLoader private constructor(
    @NonNull context: Context,
    @Nullable selection: String,
    @Nullable selectionArgs: Array<String>
) : BaseCursorLoader(
    context,
    if (isAndroidQBeforeVersion()) PROJECTION else PROJECTION_29,
    selection,
    selectionArgs,
    BUCKET_ORDER_BY
) {

    companion object {

        const val COLUMN_URI = "uri"
        const val COLUMN_COUNT = "count"

        private val COLUMNS = arrayOf(MediaStore.Files.FileColumns._ID, COLUMN_BUCKET_ID, COLUMN_BUCKET_DISPLAY_NAME, MediaStore.MediaColumns.MIME_TYPE, COLUMN_URI, COLUMN_COUNT)
        private val PROJECTION = arrayOf(MediaStore.Files.FileColumns._ID, COLUMN_BUCKET_ID, COLUMN_BUCKET_DISPLAY_NAME, MediaStore.MediaColumns.MIME_TYPE, "COUNT(*) AS $COLUMN_COUNT")
        private val PROJECTION_29 = arrayOf(MediaStore.Files.FileColumns._ID, COLUMN_BUCKET_ID, COLUMN_BUCKET_DISPLAY_NAME, MediaStore.MediaColumns.MIME_TYPE)

        // === params for showSingleMimeType: false ===
        private const val SELECTION = "(${MediaStore.Files.FileColumns.MEDIA_TYPE}=? $OR ${MediaStore.Files.FileColumns.MEDIA_TYPE}=?) $AND ${MediaStore.MediaColumns.SIZE}>0) GROUP BY ($COLUMN_BUCKET_ID"
        private const val SELECTION_29 = "(${MediaStore.Files.FileColumns.MEDIA_TYPE}=? $OR ${MediaStore.Files.FileColumns.MEDIA_TYPE}=?) $AND ${MediaStore.MediaColumns.SIZE}>0"
        private val SELECTION_ARGS = arrayOf("${MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE}", "${MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO}")
        // =============================================

        // === params for showSingleMimeType: true ===
        private const val SELECTION_FOR_SINGLE_MEDIA_TYPE = "${MediaStore.Files.FileColumns.MEDIA_TYPE}=? $AND ${MediaStore.MediaColumns.SIZE}>0 ) GROUP BY ($COLUMN_BUCKET_ID"
        private const val SELECTION_FOR_SINGLE_MEDIA_TYPE_29 = "${MediaStore.Files.FileColumns.MEDIA_TYPE}=? $AND ${MediaStore.MediaColumns.SIZE}>0";
        private fun getSelectionArgsForSingleMimeType(MimeType: Int) = arrayOf("$MimeType")
        // =============================================

        // === params for showSingleMimeType: true ===
        private const val SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE = "${MediaStore.Files.FileColumns.MEDIA_TYPE}=? $AND ${MediaStore.MediaColumns.SIZE}>0 $AND ${MediaStore.MediaColumns.MIME_TYPE}=?) GROUP BY ($COLUMN_BUCKET_ID"
        private const val SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29 = "${MediaStore.Files.FileColumns.MEDIA_TYPE}=? $AND ${MediaStore.MediaColumns.SIZE}>0 $AND ${MediaStore.MediaColumns.MIME_TYPE}=?"
        private fun getSelectionArgsForSingleMediaGifType() = arrayOf("${MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE}", "image/gif")
        // =============================================

        @Volatile
        private var INSTANCE: BaseCursorLoader? = null

        fun with(context: Context): BaseCursorLoader = INSTANCE ?: synchronized(this){ buildCursorLoader(context).also { INSTANCE = it }}

        private fun buildCursorLoader(context: Context): BaseCursorLoader {
            val selection: String
            val selectionArgs: Array<String>
            when {
                SelectionSpec.with().onlyShowGif() -> {
                    selection = if (isAndroidQBeforeVersion()) SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE else SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29
                    selectionArgs = getSelectionArgsForSingleMediaGifType()
                }
                SelectionSpec.with().onlyShowImages() -> {
                    selection = if (isAndroidQBeforeVersion()) SELECTION_FOR_SINGLE_MEDIA_TYPE else SELECTION_FOR_SINGLE_MEDIA_TYPE_29
                    selectionArgs = getSelectionArgsForSingleMimeType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE)
                }
                SelectionSpec.with().onlyShowVideos() -> {
                    selection = if (isAndroidQBeforeVersion()) SELECTION_FOR_SINGLE_MEDIA_TYPE else SELECTION_FOR_SINGLE_MEDIA_TYPE_29
                    selectionArgs = getSelectionArgsForSingleMimeType(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)
                }
                else -> {
                    selection = if (isAndroidQBeforeVersion()) SELECTION else SELECTION_29
                    selectionArgs = SELECTION_ARGS
                }
            }
            return AlbumLoader(context, selection, selectionArgs)
        }
    }

    override fun loadInBackground(): Cursor? {
        val albums = super.loadInBackground()
        val allAlbum = MatrixCursor(COLUMNS)
        if (isAndroidQBeforeVersion()) {
            var totalCount = 0
            var allAlbumCoverUri: Uri? = null
            val otherAlbums = MatrixCursor(COLUMNS)
            if (albums != null) {
                while (albums.moveToNext()) {
                    val fileId = albums.getLong(albums.getColumnIndex(MediaStore.Files.FileColumns._ID))
                    val bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID))
                    val bucketDisplayName = albums.getString(albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME))
                    val mimeType = albums.getString(albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE))
                    val uri = getUri(albums)
                    val count = albums.getInt(albums.getColumnIndex(COLUMN_COUNT))
                    otherAlbums.addRow(arrayOf("$fileId", "$bucketId", bucketDisplayName, mimeType, uri.toString(), "$count"))
                    totalCount += count
                }
                if (albums.moveToFirst()) {
                    allAlbumCoverUri = getUri(albums)
                }
            }
            allAlbum.addRow(arrayOf(AlbumEntity.ALBUM_ID_ALL, AlbumEntity.ALBUM_ID_ALL, AlbumEntity.ALBUM_NAME_ALL, null, allAlbumCoverUri, "$totalCount"))
            return MergeCursor(arrayOf<Cursor>(allAlbum, otherAlbums))
        } else {
            var totalCount = 0L
            var allAlbumCoverUri: Uri? = null
            // Pseudo GROUP BY
            val countMap = hashMapOf<Long, Long>()
            if (albums != null) {
                while (albums.moveToNext()) {
                    val bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID))
                    var count = countMap[bucketId]
                    if (count == null) {
                        count = 1L
                    } else {
                        count++
                    }
                    countMap[bucketId] = count
                }
            }
            val otherAlbums = MatrixCursor(COLUMNS)
            if (albums != null) {
                if (albums.moveToFirst()) {
                    allAlbumCoverUri = getUri(albums)
                    val done = hashSetOf<Long>()
                    do {
                        val bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID))
                        if (done.contains(bucketId)) continue
                        val fileId = albums.getLong(albums.getColumnIndex(MediaStore.Files.FileColumns._ID))
                        val bucketDisplayName = albums.getString(albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME))
                        val mimeType = albums.getString(albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE))
                        val uri = getUri(albums)
                        val count = countMap[bucketId] ?: 0L
                        otherAlbums.addRow(arrayOf("$fileId", "$bucketId", bucketDisplayName, mimeType, uri.toString(), "$count"))
                        done.add(bucketId)
                        totalCount += count
                    } while (albums.moveToNext())
                }
            }
            allAlbum.addRow(arrayOf(AlbumEntity.ALBUM_ID_ALL, AlbumEntity.ALBUM_ID_ALL, AlbumEntity.ALBUM_NAME_ALL, null, allAlbumCoverUri, "$totalCount"))
            return MergeCursor(arrayOf<Cursor>(allAlbum, otherAlbums))
        }
    }
}